//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Text;
using OpenLS.Spreadsheet.Expressions;

namespace OpenLS.Spreadsheet.Parsing
{
    class A1NamingConvention: CellNamingConvention
    {
        public override bool TryParse(string s, out CellAddress result)
        {
            int column;
            int length;
            result = new CellAddress();
            if (TryParseColumn(s, out column, out length))
            {
                int row;
                if (int.TryParse(s.Substring(length), out row))
                {
                    row--;
                    if (row < 0)
                        return false;
                    result = new CellAddress(row, column);
                    return true;
                }
            }
            return false;
        }

        public override bool TryParseColumn(string s, out int columnIndex, out int length)
        {
            Contract.Assert(s != null);
            int letterCount = 0;
            foreach(char c1 in s)
            {
                if (char.IsLetter(c1))
                    letterCount++;
                else
                    break;
            }
            if (letterCount > 3)
            {
                length = 0;
                columnIndex = 0;
                return false;
            }
            s = s.ToUpper();
            if (string.IsNullOrEmpty(s))
            {
                columnIndex = 0;
                length = 0;
                return false;
            }
            char c = s[0];
            length = 0;
            //bool relative = true; //\\ use later
            if (c == LockCharacter)
            {
                //relative = false;
                length++;
                if (length >= s.Length)
                {
                    columnIndex = -1;
                    return false;
                }
            }
            columnIndex = -1;
            bool found = false;
            while (length < s.Length)
            {
                c = s[length];
                if (char.IsLower(c))
                    columnIndex = 26 * (columnIndex + 1) + (c - 'a');
                if (char.IsUpper(c))
                    columnIndex = 26 * (columnIndex + 1) + (c - 'A');
                else
                {
                    if (!found)
                        return false;
                    return columnIndex <= Worksheet.MaxColumnIndex;
                }
                found = true;
                length++;
            }
            //\\length--;
            return columnIndex <= Worksheet.MaxColumnIndex;
        }

        public string ColumnName(int columnIndex)
        {
            return ColumnConverter.IndexToName(columnIndex);
        }

        public string RowName(int rowIndex)
        {
            return (rowIndex + 1).ToString();
        }

        public override bool RowBeforeColumn
        {
            get { return false; }
        }

        internal override List<ReferenceToken> ParseRangeReference(ExpressionParser parser)
        {
            ReferenceToken token = GetReferenceToken(parser);
            if (token == null)
                return null;
            var tokens = new List<ReferenceToken> {token};
            var t = parser.CurrentToken;
            while (parser.Has(TokenId.Colon))
            {
                token = GetReferenceToken(parser);
                if (token == null)
                {
                    parser.CurrentToken = t;
                    break;
                }
                t = parser.CurrentToken;
                tokens.Add(token);
            }

            return (tokens);
        }

        private ReferenceToken GetReferenceToken(ExpressionParser parser)
        {
            Contract.Ensures(Contract.Result<ReferenceToken>() != null || Contract.OldValue(parser.CurrentToken) == parser.CurrentToken);
            var token = parser.CurrentToken;
            bool locked = parser.Has(TokenId.Dollar);
            var tokenType = parser.NextTokenType();
            switch (tokenType)
            {
                case TokenId.Identifier:
                    int columnIndex;
                    int columnLength;
                    string id = parser.Identifier().Value;
                    if (!this.TryParseColumn(id, out columnIndex, out columnLength))
                    {
                        parser.CurrentToken = token;
                        return null;
                    }
                    if (columnLength < id.Length)
                    {
                        string rowName = id.Substring(columnLength);
                        int rowIndex;
                        if (!int.TryParse(rowName, out rowIndex))
                        {
                            parser.CurrentToken = token;
                            return null;
                        }
                        rowIndex = rowIndex - 1;
                        if (rowIndex < 0)
                        {
                            parser.CurrentToken = token;
                            return null;
                        }
                        return new CellReferenceToken(rowIndex, false, columnIndex, locked);
                    }
                    bool secondDollar = parser.Has(TokenId.Dollar);
                    if (parser.NextIs(TokenId.Numeric))
                    {
                        int rowIndex = (int) parser.ReadInteger() -1;
                        return new CellReferenceToken(rowIndex, secondDollar, columnIndex, locked);
                    }
                    if (!parser.Has(TokenId.Colon))
                    {
                        parser.CurrentToken = token;
                        return null;
                    }
                    secondDollar = parser.Has(TokenId.Dollar);
                     id = parser.Identifier().Value;
                    int columnIndex2 = ParseColumn(id);

                    return new ColumnReferenceToken(columnIndex, locked, columnIndex2, secondDollar);
                case TokenId.Numeric:
                    {
                        var n = parser.Numeric();
                        int rowIndex = (int)(double) n.Value - 1;
                        if (!parser.Has(TokenId.Colon))
                        {
                            parser.CurrentToken = token;
                            return null;
                        }
                        bool secondLock = parser.Has(TokenId.Dollar);
                        int rowIndex2 = (int)(double) parser.Numeric().Value - 1;
                        return new RowReferenceToken(rowIndex, locked, rowIndex2, secondLock);
                    }

            }
                parser.CurrentToken = token;
                return null;
        }

        internal override CellReferenceToken ParseCellReference(string text)
        {
            var ss = new CompositeFormula(text);
            //string[] ss = s.Split('!');
            if (ss.HasWorksheet)
            {
                throw new NotImplementedException();
            }
            if (ss.IsError)
            {
                return null;
            }
            text = text.Trim();
            if (text.Length == 0)
                return null;
            bool lockColumn;
            if (text[0] == LockCharacter)
            {
                lockColumn = true;
                if (text.Length == 1)
                    return null;
                text = text.Substring(1);
            }
            else
            {
                lockColumn = false;
            }
            int len;
            int columnIndex;
            if (!TryParseColumn(text, out columnIndex, out len))
                return null;
            if (len == text.Length)
                return null;
            text = text.Substring(len);
            bool lockRow;
            if (text[0] == LockCharacter)
            {
                lockRow = true;
                if (text.Length == 1)
                    return null;
                text = text.Substring(1);
            }
            else
                lockRow = false;
            int row;
            if (!int.TryParse(text, out row))
                return null;
            if (row <= 0)
                return null;
            return new CellReferenceToken(row - 1, lockRow, columnIndex, lockColumn);
        }

        public override string GetText(CellReferenceToken token)
        {
            var sb = new StringBuilder();
            if (token.LockColumn)
                sb.Append("$");
            sb.Append(ColumnName(token.Column));
            if (token.LockRow)
                sb.Append("$");
            sb.Append(RowName(token.Row));
            return sb.ToString();

        }

        public override string GetColumnName(int index, bool locked)
        {
            string result = "";
            if (locked)
                result = "$";
            result += ColumnConverter.IndexToName(index);
            return result;
        }

        public override string GetRowName(int index, bool locked)
        {
            string result = "";
            if (locked)
                result = "$";
            result += (index + 1).ToString();
            return result;
        }

        bool tryParseRowCol(string s, Worksheet ws, out AnchoredRowReference row, out AnchoredColumnReference column)
        {
            s = s.Trim();
            row = null;
            column = null;
            if (s.Length == 0)
                return false;
            bool lockColumn;
            if (s[0] == LockCharacter)
            {
                lockColumn = true;
                s = s.Substring(1);
                if (s.Length == 0)
                    return false;

            }
            else
                lockColumn = false;
            int len;
            int columnIndex;
            if (TryParseColumn(s, out columnIndex, out len))
            {
                column = new AnchoredColumnReference(ws.Columns[columnIndex], lockColumn);
                if (len == s.Length)
                {
                    return true;
                }
                s = s.Substring(len);
                bool lockRow;
                if (s[0] == LockCharacter)
                {
                    lockRow = true;
                    s = s.Substring(1);
                    if (s.Length == 0)
                        return false;
                }
                else lockRow = false;
                int rowIndex;
                if (int.TryParse(s, out rowIndex))
                {
                    if (rowIndex < 1)
                        return false;
                    row = new AnchoredRowReference(ws.Rows[rowIndex - 1], lockRow);
                    return true;
                }
                return false;
            }
            else
            {
                int rowIndex;
                if (int.TryParse(s, out rowIndex))
                {
                    if (rowIndex < 1)
                        return false;
                    row = new AnchoredRowReference(ws.Rows[rowIndex - 1], lockColumn);
                    return true;
                }
                return false;
            }
        }

        internal bool TryParseWorksheetCellReference(string s, ExpressionParser parser, Worksheet ws, out CellReference rr)
        {
            Contract.Requires(s != null);

            var ss = new CompositeFormula(s);
            //string[] ss = s.Split('!');
            if (ss.HasWorksheet)
            {
                Worksheet ws2 = ws.Workbook.GetWorksheet(ss.WorksheetName); //\\ FindWorksheet(ws.Workbook, ss.WorksheetName);
                rr = null;
                if (ws2 == null)
                    return false;
                return TryParseWorksheetCellReference(ss.Formula, parser, ws2, out rr);
            }
            if (ss.IsError)
            {
                rr = null;
                return false;
            }
            s = s.Trim();
            rr = null;
            if (s.Length == 0)
                return false;
            bool lockColumn;
            if (s[0] == LockCharacter)
            {
                lockColumn = true;
                if (s.Length == 1)
                    return false;
                s = s.Substring(1);
            }
            else
            {
                lockColumn = false;
            }
            int len;
            int columnIndex;
            if (!TryParseColumn(s, out columnIndex, out len))
                return false;
            if (len == s.Length)
                return false;
            s = s.Substring(len);
            bool lockRow;
            if (s[0] == LockCharacter)
            {
                lockRow = true;
                if (s.Length == 1)
                    return false;
                s = s.Substring(1);
            }
            else
                lockRow = false;
            int row;
            if (!int.TryParse(s, out row))
                return false;
            if (row <= 0)
                return false;
            rr = new DynamicCellReference(new AnchoredRowReference(ws.Rows[row - 1], lockRow), new AnchoredColumnReference(ws.Columns[columnIndex], lockColumn));
            return true;
        }

        const char LockCharacter = '$';

        /* private static Worksheet FindWorksheet(Workbook wb, string ss)
        {
            return wb.GetWorksheet(ss.Trim('\''));
        }
        */
        internal bool TryParseCellReference(string s, ExpressionParser parser, Workbook wb, out CellReference rr)
        {
            Contract.Requires(s != null);

            var ss = new CompositeFormula(s);
            if (ss.IsError)
            {
                rr = null;
                return false;
            }
            //string[] ss = s.Split('!');
            rr = null;
            Worksheet ws = wb.GetWorksheet(ss.WorksheetName); //\\FindWorksheet(wb, ss.WorksheetName);
            if (ws == null)
                return false;
            return TryParseWorksheetCellReference(ss.Formula, parser, ws, out rr);
        }
    }
}
